package org.tinygroup.jdbctemplatedslsession;

import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.util.List;

import javax.sql.DataSource;

import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.core.PreparedStatementCreator;
import org.springframework.jdbc.core.SqlTypeValue;
import org.springframework.jdbc.core.StatementCreatorUtils;
import org.springframework.jdbc.support.GeneratedKeyHolder;
import org.springframework.jdbc.support.KeyHolder;
import org.springframework.jdbc.support.incrementer.DataFieldMaxValueIncrementer;
import org.tinygroup.commons.tools.Assert;
import org.tinygroup.commons.tools.CollectionUtil;
import org.tinygroup.jdbctemplatedslsession.provider.DefaultPrimaryKeyProvider;
import org.tinygroup.jdbctemplatedslsession.rowmapper.SimpleRowMapperSelector;
import org.tinygroup.logger.LogLevel;
import org.tinygroup.logger.Logger;
import org.tinygroup.logger.LoggerFactory;
import org.tinygroup.tinysqldsl.ComplexSelect;
import org.tinygroup.tinysqldsl.Delete;
import org.tinygroup.tinysqldsl.DslSession;
import org.tinygroup.tinysqldsl.Insert;
import org.tinygroup.tinysqldsl.Select;
import org.tinygroup.tinysqldsl.Update;
import org.tinygroup.tinysqldsl.base.Column;
import org.tinygroup.tinysqldsl.base.DefaultKeyHolder;
import org.tinygroup.tinysqldsl.base.DslKeyHolder;
import org.tinygroup.tinysqldsl.base.InsertContext;
import org.tinygroup.tinysqldsl.insert.InsertBody;

/**
 * DslSqlSession接口的jdbctemplate版实现
 * 
 * @author renhui
 * 
 */
public class SimpleDslSession implements DslSession {

	private JdbcTemplate jdbcTemplate;
	private PrimaryKeyProvider provider;
	private DataFieldMaxValueIncrementer incrementer;
	private RowMapperSelector selector = new SimpleRowMapperSelector();
	private static final Logger LOGGER = LoggerFactory
			.getLogger(SimpleDslSession.class);

	public SimpleDslSession(DataSource dataSource) {
		jdbcTemplate = new JdbcTemplate(dataSource);
		provider = new DefaultPrimaryKeyProvider();
	}
	
	public SimpleDslSession(DataSource dataSource,RowMapperSelector selector) {
		this(dataSource);
		this.selector = selector;
	}

	public RowMapperSelector getSelector() {
		return selector;
	}

	public DataFieldMaxValueIncrementer getIncrementer() {
		return incrementer;
	}

	public void setIncrementer(DataFieldMaxValueIncrementer incrementer) {
		this.incrementer = incrementer;
	}

	public void setSelector(RowMapperSelector selector) {
		this.selector = selector;
	}

	public JdbcTemplate getJdbcTemplate() {
		return jdbcTemplate;
	}

	public int execute(Insert insert) {
		logMessage(insert.sql(), insert.getValues());
		return jdbcTemplate.update(insert.sql(), insert.getValues().toArray());
	}

	/**
	 * 由应用生成的主键只支持单个主键
	 */
	public DslKeyHolder executeAndReturnKey(final Insert insert,
			boolean autoGeneratedKeys) {
		InsertContext context = insert.getContext();
		final String[] keyNames = provider.generatedKeyNamesWithMetaData(
				jdbcTemplate.getDataSource(), context.getSchema(), null,
				context.getTableName());
		if (keyNames == null || keyNames.length == 0) {
			throw new IllegalArgumentException(String.format("表格：%s，不存在主键字段",
					context.getTableName()));
		}
		if (autoGeneratedKeys) {
			return autoGeneratedKeys(insert, keyNames);
		}
		if (keyNames.length != 1) {
			throw new IllegalArgumentException("应用生成主键方式不支持复合主键");
		}
		if (!context.existParam(keyNames[0])) {
			InsertContext newContext = context.copyContext();
			return appGeneratedKey(keyNames[0], newContext);

		} else {
			Object value = context.getParamValue(keyNames[0]);
			if(value!=null){
				return new DefaultKeyHolder(keyNames[0],value);
			}
		}
		return null;
	}

	private DslKeyHolder appGeneratedKey(String keyName, InsertContext context) {
		Assert.assertNotNull(incrementer, "incrementer must not be null");
		Object value = incrementer.nextLongValue();// 目前暂时不根据主键类型去调用incrementer接口的不同方法。
		Column primaryColumn = new Column(context.getTable(), keyName);
		context.addValues(primaryColumn.value(value));
		InsertBody insertBody = context.createInsert();
		jdbcTemplate.update(insertBody.toString(), context.getParamValues());
		return new DefaultKeyHolder(keyName,value);
	}

	private DslKeyHolder autoGeneratedKeys(final Insert insert,
			final String[] keyNames) {
		KeyHolder keyHolder = new GeneratedKeyHolder();
		jdbcTemplate.update(new PreparedStatementCreator() {
			public PreparedStatement createPreparedStatement(Connection con)
					throws SQLException {
				PreparedStatement ps = con.prepareStatement(insert.sql(),
						keyNames);
				setParameterValues(ps, insert.getValues(), null);
				return ps;
			}
		}, keyHolder);
		return new SimpleDslKeyHolder(keyHolder);
	}

	/**
	 * Internal implementation for setting parameter values
	 * 
	 * @param preparedStatement
	 *            the PreparedStatement
	 * @param values
	 *            the values to be set
	 */
	private void setParameterValues(PreparedStatement preparedStatement,
			List<Object> values, int[] columnTypes) throws SQLException {
		int colIndex = 0;
		for (Object value : values) {
			colIndex++;
			if (columnTypes == null || colIndex < columnTypes.length) {
				StatementCreatorUtils.setParameterValue(preparedStatement,
						colIndex, SqlTypeValue.TYPE_UNKNOWN, value);
			} else {
				StatementCreatorUtils.setParameterValue(preparedStatement,
						colIndex, columnTypes[colIndex - 1], value);
			}
		}
	}

	private void logMessage(String sql, List<Object> values) {
		LOGGER.logMessage(LogLevel.DEBUG, "Executing SQL:[{0}],values:{1}",
				sql, values);
	}

	public int execute(Update update) {
		logMessage(update.sql(), update.getValues());
		return jdbcTemplate.update(update.sql(), update.getValues().toArray());
	}

	public int execute(Delete delete) {
		logMessage(delete.sql(), delete.getValues());
		return jdbcTemplate.update(delete.sql(), delete.getValues().toArray());
	}

	@SuppressWarnings("unchecked")
	public <T> T fetchOneResult(Select select, Class<T> requiredType) {
		logMessage(select.sql(), select.getValues());
		return (T) jdbcTemplate.queryForObject(select.sql(), select.getValues()
				.toArray(), selector.rowMapperSelector(requiredType));
	}

	@SuppressWarnings("unchecked")
	public <T> T[] fetchArray(Select select, Class<T> requiredType) {
		List<T> records = fetchList(select, requiredType);
		if (!CollectionUtil.isEmpty(records)) {
			return (T[]) records.toArray();
		}
		return null;
	}

	@SuppressWarnings("unchecked")
	public <T> List<T> fetchList(Select select, Class<T> requiredType) {
		logMessage(select.sql(), select.getValues());
		return jdbcTemplate.query(select.toString(), select.getValues()
				.toArray(), selector.rowMapperSelector(requiredType));
	}

	@SuppressWarnings("unchecked")
	public <T> T[] fetchArray(ComplexSelect complexSelect, Class<T> requiredType) {
		List<T> records = fetchList(complexSelect, requiredType);
		if (!CollectionUtil.isEmpty(records)) {
			return (T[]) records.toArray();
		}
		return null;
	}

	@SuppressWarnings("unchecked")
	public <T> List<T> fetchList(ComplexSelect complexSelect,
			Class<T> requiredType) {
		logMessage(complexSelect.sql(), complexSelect.getValues());
		return jdbcTemplate.query(complexSelect.toString(), complexSelect
				.getValues().toArray(), selector
				.rowMapperSelector(requiredType));
	}

	@SuppressWarnings("unchecked")
	public <T> T fetchOneResult(ComplexSelect complexSelect,
			Class<T> requiredType) {
		logMessage(complexSelect.sql(), complexSelect.getValues());
		return (T) jdbcTemplate.queryForObject(complexSelect.sql(),
				complexSelect.getValues().toArray(),
				selector.rowMapperSelector(requiredType));
	}

}
